הסבר איך ליצור סביבת פיתוח אישית שעובדת על בסיס MVC.
במקרה ולא קראתם את החלקים הקודמים חלק ראשון וחלק שני.
בחלק הזה אני יגע במודלים
מזה בעצם מודל?
מודל בעצם מבצע את הבדיקות של המערכת אם זה טפסים ואם זה בדיקות מול מסד נתונים,בחלק הזה אני יגע במודל פשוט לעבודה מול מסד נתונים.
איכן בעצם אנחנו משתמשים במודל?
בקבצי הקונטרולייר שלנו היה משתנה של המחלקה שמקבל מחלקה שיורשת ממודל.
איך זה בעצם עובד?
יש לנו קובץ model בתיקיית library שבעצם זאת במחלקה הזאת יש כלים לבדיקת אימותים של טפסים ומסדי נתונים.
יש במחלקה שיורשת ממחלקת ממחלקת model והיא מוגרת לבצע את הפעולות הדרושות.
המחלקה מוגדרת בתוך הקונטרולייר במשתנה model ומבצעת את הפעולות לפי הגדרת הקונטרולייר.
אוקי אז נתחיל בעבודה על הקוד:
נפתח שוב את Controller.php שנמצא בתיקיית library:
ונוסיף לו את הפונקציה:
/*
* @param (string) $name the name of model
* @param (model) $model object model
* set the path of the model folder
* set the object model on $model
*/
public function loadModel($name)
{
$path = APPLICATION . DS . 'models' . DS . $name . '.php';
if (file_exists($path)) {
require($path);
$modelname = ucfirst($name) . '_Model';
$this->model = new $modelname();
}
}
* @param (string) $name the name of model
* @param (model) $model object model
* set the path of the model folder
* set the object model on $model
*/
public function loadModel($name)
{
$path = APPLICATION . DS . 'models' . DS . $name . '.php';
if (file_exists($path)) {
require($path);
$modelname = ucfirst($name) . '_Model';
$this->model = new $modelname();
}
}
קובץ חדש Model.php בתיקיית libary:
<?php
class Framework_Model
{
public $db;
function __construct()
{
//use the database class for use on all models
$this->db = Framework_Model_Database(get_child_name());
}
}
class Framework_Model
{
public $db;
function __construct()
{
//use the database class for use on all models
$this->db = Framework_Model_Database(get_child_name());
}
}
קובץ database.php בתיקייה library:
זאת מחלקה שעבדתי עליה בלפני כמה שבועות יש לה שימוש נחמד לניהול הDB
<?php
/*
* **************************
* Sidox db model *
* **************************
*
* how to use:
* 1)connected to sql server
* demo for mysql:
* $obj = new Sidox_Model_Database('table'(=null));
* 2)set table
* $obj->table = "tablename";
* 3)for add new information in db use
* $obj->dbColum = 'x';
* $obj->dbColum2 = 'y';
* $obj->save();
* 4)for update information in db use
* $obj->dbColum = 'y';
* $obj->dbColum2 = 'x';
* $obj->save('dbColum = x','dbColum2 = y');
* 5)delete information
* $obj->delete('dbColum = y','dbColum2 = x');
* 6)get information
* $obj->getRows('dbColum = y','dbColum2 = x');
* $obj->getRows(); //get all
* 7)order by and limit
* $obj->order('dbColum','DESC')->limit(0,2)->getRows('dbColum = y','dbColum2 = x');
* $obj->order('dbColum','DESC')->limit(0,2)->getRows();
*/
class Framework_Model_Database extends PDO {
/*
* @param (string) $_data Linking the database with data
*/
private $_data = null;
/*
* @param (string) $_scan scan in db
*/
private $_scan = null;
/*
* @param (string) $table database table
*/
public $table;
/*
* use parent construct and set table
*/
function __construct($table = null) {
parent::__construct(General::Conf('DBtype').':host='.General::Conf('DBhost').';dbname='.General::Conf('DBname'), General::Conf('DBuser'), General::Conf('DBpassword'));
$this->table = $table;
}
/*
* save params in $_data
* @param $name name of db column
* @param $value the colum value
*/
function __set($name, $value) {
if ($this->_action == 'update') {
$this->_data[] = '`' . $name . '` = \'' . $value . '\'';
}
$this->_data[$name] = " '$value' ";
}
/*
* add mode: save in db new information
* update mode: update the information
* @param $_data save the information
* @param $_update save the mode of function (true = update, false = add)
* @param func_args where to scan for update
* @param $table db table
* @param $rtn return data
* @return false or query
*/
public function save() {
$rtn = false;
if ((empty($this->_data)) && (empty($this->table))) {
return false;
} elseif (func_num_args() > 0) {
//action update
$update = null;
foreach ($this->_data as $key => $value) {
if (empty($update)) {
$update = " `{$key}` = '{$value}'";
continue;
}
$update .= " , `{$key}` = '{$value} '";
}
$rtn = $this->query(
'UPDATE `' . $this->table . '` SET ' .
$update . $this->scanWhere(func_get_args())
);
} else {
//action add
$rtn = $this->query(
'INSERT INTO `' . $this->table . '` (
' . implode(' , ', array_keys($this->_data)) . ' ) VALUES (
' . implode(' , ', $this->_data) . ' )'
);
}
$this->_data = null;
return $rtn;
}
/*
* delete db row
* @param func_args
* @return the query connection
*/
public function delete() {
if (func_num_args() > 0)
return $this->query(
'DELETE FROM `' . $this->table .
$this->scanWhere(func_get_args())
);
}
/*
* get DB rows
* @param func_args or null
* @return the query connection
*/
public function getRows() {
$sql = 'SELECT * FROM `' . $this->table . '` ';
if (func_num_args() > 0)
$sql .= $this->scanWhere(func_get_args());
if (!empty($this->_sets)) {
$sql .= " " . $this->_sets;
unset($this->_sets);
}
unset($this->_scan);
return $this->query($sql);
}
/*
* Order By
* @param string $order what order
* @param string $type is ASC or DESC
* @param $_sets test if set if true function die
* @return $this
*/
public function order($order, $type = 'ASC') {
if (isset($this->_sets['order'])) {
return false;
}
$this->_scan .= ' ORDER BY ' . $order . ' ' . $type . ' ';
$this->_sets['order'] = null;
return $this;
}
/*
* limit list
* @param int $limit start limit list or mach of limit
* @param int $endLimit end the limit list
* @param $_sets test if set if true function die
* @return $this
*/
public function limit($limit, $endLint = null) {
if (isset($this->_sets['limit'])) {
return false;
}
if (!empty($endLint))
$this->_scan .= ' LIMIT ' . $limit . ',' . $endLint;
else
$this->_scan .= ' LIMIT ' . $limit;
$this->_sets['limit'] = null;
return $this;
}
/*
* @param Array $scanArgs where scan
* @return where to scan
*/
private function scanWhere($scanArgs) {
for ($i = 0; $i < sizeof($scanArgs); $i++) {
sscanf($scanArgs[$i], '%s %s %s', $key, $operator, $value);
$scanArgs[$i] = ' `' . $key . '` ' . $operator . ' \'' . $value . '\'';
}
return ' WHERE ' . implode(' AND ', $scanArgs);
}
}
/*
* **************************
* Sidox db model *
* **************************
*
* how to use:
* 1)connected to sql server
* demo for mysql:
* $obj = new Sidox_Model_Database('table'(=null));
* 2)set table
* $obj->table = "tablename";
* 3)for add new information in db use
* $obj->dbColum = 'x';
* $obj->dbColum2 = 'y';
* $obj->save();
* 4)for update information in db use
* $obj->dbColum = 'y';
* $obj->dbColum2 = 'x';
* $obj->save('dbColum = x','dbColum2 = y');
* 5)delete information
* $obj->delete('dbColum = y','dbColum2 = x');
* 6)get information
* $obj->getRows('dbColum = y','dbColum2 = x');
* $obj->getRows(); //get all
* 7)order by and limit
* $obj->order('dbColum','DESC')->limit(0,2)->getRows('dbColum = y','dbColum2 = x');
* $obj->order('dbColum','DESC')->limit(0,2)->getRows();
*/
class Framework_Model_Database extends PDO {
/*
* @param (string) $_data Linking the database with data
*/
private $_data = null;
/*
* @param (string) $_scan scan in db
*/
private $_scan = null;
/*
* @param (string) $table database table
*/
public $table;
/*
* use parent construct and set table
*/
function __construct($table = null) {
parent::__construct(General::Conf('DBtype').':host='.General::Conf('DBhost').';dbname='.General::Conf('DBname'), General::Conf('DBuser'), General::Conf('DBpassword'));
$this->table = $table;
}
/*
* save params in $_data
* @param $name name of db column
* @param $value the colum value
*/
function __set($name, $value) {
if ($this->_action == 'update') {
$this->_data[] = '`' . $name . '` = \'' . $value . '\'';
}
$this->_data[$name] = " '$value' ";
}
/*
* add mode: save in db new information
* update mode: update the information
* @param $_data save the information
* @param $_update save the mode of function (true = update, false = add)
* @param func_args where to scan for update
* @param $table db table
* @param $rtn return data
* @return false or query
*/
public function save() {
$rtn = false;
if ((empty($this->_data)) && (empty($this->table))) {
return false;
} elseif (func_num_args() > 0) {
//action update
$update = null;
foreach ($this->_data as $key => $value) {
if (empty($update)) {
$update = " `{$key}` = '{$value}'";
continue;
}
$update .= " , `{$key}` = '{$value} '";
}
$rtn = $this->query(
'UPDATE `' . $this->table . '` SET ' .
$update . $this->scanWhere(func_get_args())
);
} else {
//action add
$rtn = $this->query(
'INSERT INTO `' . $this->table . '` (
' . implode(' , ', array_keys($this->_data)) . ' ) VALUES (
' . implode(' , ', $this->_data) . ' )'
);
}
$this->_data = null;
return $rtn;
}
/*
* delete db row
* @param func_args
* @return the query connection
*/
public function delete() {
if (func_num_args() > 0)
return $this->query(
'DELETE FROM `' . $this->table .
$this->scanWhere(func_get_args())
);
}
/*
* get DB rows
* @param func_args or null
* @return the query connection
*/
public function getRows() {
$sql = 'SELECT * FROM `' . $this->table . '` ';
if (func_num_args() > 0)
$sql .= $this->scanWhere(func_get_args());
if (!empty($this->_sets)) {
$sql .= " " . $this->_sets;
unset($this->_sets);
}
unset($this->_scan);
return $this->query($sql);
}
/*
* Order By
* @param string $order what order
* @param string $type is ASC or DESC
* @param $_sets test if set if true function die
* @return $this
*/
public function order($order, $type = 'ASC') {
if (isset($this->_sets['order'])) {
return false;
}
$this->_scan .= ' ORDER BY ' . $order . ' ' . $type . ' ';
$this->_sets['order'] = null;
return $this;
}
/*
* limit list
* @param int $limit start limit list or mach of limit
* @param int $endLimit end the limit list
* @param $_sets test if set if true function die
* @return $this
*/
public function limit($limit, $endLint = null) {
if (isset($this->_sets['limit'])) {
return false;
}
if (!empty($endLint))
$this->_scan .= ' LIMIT ' . $limit . ',' . $endLint;
else
$this->_scan .= ' LIMIT ' . $limit;
$this->_sets['limit'] = null;
return $this;
}
/*
* @param Array $scanArgs where scan
* @return where to scan
*/
private function scanWhere($scanArgs) {
for ($i = 0; $i < sizeof($scanArgs); $i++) {
sscanf($scanArgs[$i], '%s %s %s', $key, $operator, $value);
$scanArgs[$i] = ' `' . $key . '` ' . $operator . ' \'' . $value . '\'';
}
return ' WHERE ' . implode(' AND ', $scanArgs);
}
}
בקובץ Bootstrap.php:
לשנות את הפונקציה:
function runController($name, $method = NULL) {
/*
* cheak if controller exists
* TRUE:(controller not exists)
* run diagnostics
* go to error page -> move to default page
*/
$Controller = ucfirst($name) . "Controller";
$dirController = APPLICATION . 'controllers' . DS .
$Controller . '.php';
if (!(file_exists($dirController))) {
//if file not exists it's 404 page
echo "404"; //you can make 404 page controller or header...
return FALSE;
}
/*
* run contoller
*/
require $dirController;
$Controller = new $Controller;
$Controller->loadModel($name); //load the model
//cheak if meathod exists
if (!(method_exists($Controller, $method)))
$method = "Main"; //method not exist set defualt
$Controller->{$method}(); //run action
}
/*
* cheak if controller exists
* TRUE:(controller not exists)
* run diagnostics
* go to error page -> move to default page
*/
$Controller = ucfirst($name) . "Controller";
$dirController = APPLICATION . 'controllers' . DS .
$Controller . '.php';
if (!(file_exists($dirController))) {
//if file not exists it's 404 page
echo "404"; //you can make 404 page controller or header...
return FALSE;
}
/*
* run contoller
*/
require $dirController;
$Controller = new $Controller;
$Controller->loadModel($name); //load the model
//cheak if meathod exists
if (!(method_exists($Controller, $method)))
$method = "Main"; //method not exist set defualt
$Controller->{$method}(); //run action
}
דוגמה לשימוש:
ליצור קובץ בשם main.php תחת תיקיה appliction/model
<?php
class Main_Model extends Framework_Model
{
public function cheakuser()
{
if (isset($_POST['username'])
if ($_POST['username'] == null)
echo 'the username can't be empty';
}
}
class Main_Model extends Framework_Model
{
public function cheakuser()
{
if (isset($_POST['username'])
if ($_POST['username'] == null)
echo 'the username can't be empty';
}
}
ואז כידי להשתמש בקונטרולייר MainController.php
זה קורה אוטומטית ל Main_Model ואם רוצים לקרוא למחלקה במחלקה אחרת:
$this->mainModel = $this->loadModel('main');
$this->mainModel->cheakuser();
$this->mainModel->cheakuser();
רציתי להוסיף כאן מחלקה לניהול קלט אבל עדיין אני עובד אליה בחלק הבא אז אני יגע בזה ואני יגע בטיפים לשדרות הframework ולאחר סיום הפיתוח של הframework שלי אני יפרסם אותו גם ככה שתוכלו לראות את משאני עשיתי לשפר את שלכם או להשתמש בשלי
תגובות לכתבה:
תודה על המדריך.
אמנם אני לא מסכים עם כמה דברים קונצפטואלים פה.
קונטרולר אחד צריך להיות מסוגל לעבוד עם כמה מודלים שונים. אני יכול לרצות קודם לשלוף את כל המידע על המשתמש, אחר כך להכניס משהו לטבלה של מוצרים.
שנית, הגיע הזמן לקרוא על autoloading, לא ?
http://phpguide.co.il/טעינת סקריפטים לפי דרישה autoload.htm
בנוסף אני לא רואה שום סיבה שמודל ירחיב את PDO.
מודל לא מהווה קישור לדאטאבייס.
מודל מהווה מבנה נתונים כלשהו. לדוגמה:
*משתמש* עם שם, גיל ותעודת זהות
ועוד קצת פעולות של שינוי שם, שעל הדרך בודק שבש הפרטי אין רווחים.
זה שמודל יכול להיות שמור בדאטאבייס ואתה מנסה לספק למשתמש גם ORM וגם שמירה אוטומטית וגם חיפוש - זה נחמד. אבל מודל יכול להיות מבוסס גם על קבצים ולא מסד נתונים. איך במקרה הזה?
כרגע אני לא רוצה לפרסם את זה כיוון שיש פה יותר דברים מבלבלים, מאשר מועילים.
תודה על תגובה ובקשר לautoloading הוא בשימוש בBootstrap והקוד מאפשר לעבוד אם מספר מודלים אם מבצעים loadModel בקובץ הקונטרולייר אבל כברירת מחדל הוא בודק אם מודל בשם של הקונטרלייר קיים ואם כן הוא מריץ אותו
מעט מאוד מודלים יהיו בעלי שם זהה לאחד הקונטרולרים שמשתמשים בו.
ב autoloading אפשר (וצריך) להחליף את כל הקוד של ה load model
יש מצב שתוכל לכוון אותי יותר למה לדעתך אמור להיות במודל
בצורה אידאלית? מבנה נתונים ולוגיקה של המערכת שלך
[php]
// Ideally clear DTO
class Person
{
public $name;
public $age;
}
[/php]
ושהפריימוורק שלך ישתמש ב ORM כלשהו לשמירה וטעינה שלהם.
תלוי באיזה ORM תבחר, הם יכולים לדרוש הוספה של דברים למחלקה הזאת בהתאם ל pattern שבו הם כתובים.
לרשותך כמה ORMים מוכנים שאתה יכול להכניס לפריימוורק
Doctrine, php active record, redbeans
אם אתה רוצה, אתה יכול לכתוב אחד משלך. דוגמה לשני הפטרנים העיקריים כתבתי בשבילך פה:
http://pastebin.com/iGfz82fb
כמו שאתה רואה, המודלים שהמשתמש של הפריימוורק שלך צריך לכתוב לא אמורים להכיל שום דבר מלבד dto (מחלקה שמייצגת מבנה נתונים). כל שאר הקוד נמצא ב ORM שהוא נלקח מוכן או נכתב בנפרד
לא כל כך הבנתי את שיטת העבודה הזאת
בשביל זה אני ממליץ לפני כתיבת מדריך "איך לבנות פריימוורק"
לנסות להשתמש בכמה
אני מתכוון לזה שאתה בעצם אומר ליצור מחלקה שהיא רק עם משתנים שהיא המודל ועוד מחלקה orm אבל איך הרעיון הזה עובד הכוונה שלי איך אני בודק טפסים ככה ואיך שולך שאילתות למסד צריך עוד מחלקה נוספת לזה לא? אתה יכול לתת לי דוגמה לקוד כזה
מודל יכול לכלול מבנה נתונים ולוגיקה.
בדקית טספים זה בדיוק הלוגיקה שהמודל כולל.
דוגמה למודל שלא מבוסס מסד נתונים:
http://pastebin.com/63g2bRKR
הוא נטו כולל קצת נתונים ולוגיקה לגביהם.
וככה הוא יראה בגירסת activeRecord על בסיס קובץ
http://pastebin.com/bJRrmt4n
הדוגמה השניה, של ה activerecord בהחלט כוללת גם את העבודה עם המסד (וגם וולידציה שלא רשומה שם).
הדוגמה הראשונה (unit of work) לאומת זאת דורשת ממך לכתוב מחלקה חיצונית שתתעסק בעבודה עם מסד ומחלקה חיצונית שתתעסק בוולידציה והם בתור פרמטרים יקבלו מופעים של מחלקות נתונים.
היום ה activeRecord יותר פופולארי בקרב פריימוורקים, כאשר המשתמש כותב במודל שלו את מבנה הנתונים (ומערך של פילטרים לוולידציה) ושאר פעולות העבודה עם מסד והוולידציה עצמה מגיעות אליו ממחלקות האב שמספק הפריימוורק.